package com.deschen.security.core.social.weixin.connect;

import com.deschen.security.core.vo.ResultVO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.GrantType;
import org.springframework.social.oauth2.OAuth2Parameters;
import org.springframework.social.oauth2.OAuth2Template;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author deschen
 * @Create 2019/9/9
 * @Description
 * @Since 1.0.0
 */
@Slf4j
public class WeixinOAuth2Template extends OAuth2Template {

    private String appId;

    private String secret;

    private String accessTokenUrl;

    private String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";

    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * OAuth2Template两个方法介绍
     * 通过Authorization Code获取Access Token--》exchangeForAccess(..)
     * 权限自动续期，获取Access Token--》refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters)
     * 因为微信的请求参数值不同，所以重写方法
     * postForAccessGrant(..)获取令牌，被上面两个方法调用，默认返回Map类型，而实际QQ返回String类型，要重写
     */

    public WeixinOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
        super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
        // 是否把参数传到请求中
        setUseParametersForClientAuthentication(true);
        this.appId = clientId;
        this.secret = clientSecret;
        this.accessTokenUrl = accessTokenUrl;
    }

    /**
     * 获取令牌
     * @param authorizationCode
     * @param redirectUri
     * @param additionalParameters
     * @return
     */
    @Override
    public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri, MultiValueMap<String, String> additionalParameters) {
        log.info("【微信第三方登入】微信获取令牌");
        StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);

        accessTokenRequestUrl.append("?appid=" + appId);
        accessTokenRequestUrl.append("&secret=" + secret);
        accessTokenRequestUrl.append("&code=" + authorizationCode);
        accessTokenRequestUrl.append("&grant_type=authorization_code");
        accessTokenRequestUrl.append("?redirect=" + redirectUri);
        log.info("【微信第三方登入】获取令牌请求 url=" + accessTokenRequestUrl.toString());
        return getAccessToken(accessTokenRequestUrl);
    }

    /**
     * 刷新access_token有效期
     * @param refreshToken
     * @param additionalParameters
     * @return
     */
    @Override
    public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {
        log.info("【微信第三方登入】微信刷新access_token有效期");
        StringBuilder refreshTokenRequestUrl = new StringBuilder(REFRESH_TOKEN_URL);

        refreshTokenRequestUrl.append("?appid=" + appId);
        refreshTokenRequestUrl.append("&grant_type=refresh_token");
        refreshTokenRequestUrl.append("&refresh_token=" + refreshToken);
        log.info("【微信第三方登入】刷新access_token请求 url=" + refreshTokenRequestUrl.toString());

        return getAccessToken(refreshTokenRequestUrl);
    }

    // todo 用源码方式
    private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
        log.info("【微信第三方登入】微信获取令牌公用方法 请求url = " + accessTokenUrl.toString());
        String accessTokenResponse = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
        log.info("【微信第三方登入】微信获取令牌响应 accessToeknResponse" + accessTokenResponse);
        Map<String, Object> accessTokenMap = null;
        try {
            accessTokenMap = objectMapper.readValue(accessTokenResponse, Map.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 判断是否有错误码
        String errcode = getStringByMap(accessTokenMap, "errcode");
        if (errcode != null) {
            ResultVO failure = ResultVO.failure(
                    Integer.valueOf(errcode), getStringByMap(accessTokenMap, "errmsg"));
            String result = null;
            try {
                result = objectMapper.writeValueAsString(failure);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            throw new RuntimeException(result);
        }

        // 构建令牌
        WeixinAccessGrant weixinAccessGrant = new WeixinAccessGrant(
                getStringByMap(accessTokenMap, "access_token"),
                getStringByMap(accessTokenMap, "scope"),
                getStringByMap(accessTokenMap, "refresh_token"),
                Long.valueOf(getStringByMap(accessTokenMap, "expires_in")),
                getStringByMap(accessTokenMap, "openid")
        );
        return weixinAccessGrant;
    }

    /**
     * 构建获取授权码的请求。也就是引导用户跳转到微信的地址。
     * @param parameters
     * @return
     */
    @Override
    public String buildAuthorizeUrl(OAuth2Parameters parameters) {
        return this.buildAuthorizeUrl(parameters);
    }

    @Override
    public String buildAuthenticateUrl(OAuth2Parameters parameters) {
        String url = super.buildAuthenticateUrl(parameters);
        url += "&appid=" + appId + "&scope=snsapi_login";
        log.info("【微信第三方登入】 跳转微信地址 url = " + url);
        return url;
    }

    /**
     * 微信返回contentType是html/text，添加相应HttpMessageConverter来处理
     * @return
     */
    @Override
    protected RestTemplate createRestTemplate() {
        RestTemplate restTemplate = super.getRestTemplate();
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    }

    private String getStringByMap(Map<String, Object> map, String value) {
        if (map.get(value) != null) {
            return map.get(value).toString();
        }
        return null;
    }

}
